﻿using System;
using System.Security.Cryptography;

namespace GJQT.ZPK
{
    internal class TinyCipher : BlockCipher
    {
        protected readonly uint[] K = new uint[4];
        protected readonly uint rounds;

        public TinyCipher(byte[] key, int keyOffset, int keyLength)
            : base(CryptoStreamMode.Read)
        {
            if (key == null)
                throw new ArgumentNullException("key");
            if (keyOffset < 0)
                throw new ArgumentOutOfRangeException("keyOffset");
            if (keyLength < 0 || keyOffset + keyLength > key.Length)
                throw new ArgumentOutOfRangeException("keyLength");

            K[0] = 0xC33707D6;
            K[1] = 0x4BDECFA9;
            K[2] = 0xFC93A039;
            K[3] = 0xE7D3FBC8;
            for (int i = 0; i < keyLength; i++)
            {
                K[i & 3] ^= key[keyOffset + i];
            }
            rounds = ((K[0] ^ K[1] ^ K[2] ^ K[3]) & 0x0f) + 0x10;
        }

        protected override void DecryptBlock(ref uint v0, ref uint v1)
        {
            uint delta = 0x9E3779B9;
            uint sum = delta * rounds;
            for (int i = 0; i < rounds; i++)
            {
                v1 -= ((v0 << 4) + K[2]) ^ (v0 + sum) ^ ((v0 >> 5) + K[3]);
                v0 -= ((v1 << 4) + K[0]) ^ (v1 + sum) ^ ((v1 >> 5) + K[1]);
                sum -= delta;
            }
        }
    }

    internal class ExtendedTinyCipher : TinyCipher
    {
        public ExtendedTinyCipher(byte[] key, int keyOffset, int keyLength)
            : base(key, keyOffset, keyLength)
        {
        }

        protected override void DecryptBlock(ref uint v0, ref uint v1)
        {
            uint delta = 0x9E3779B9;
            uint sum = delta * rounds;
            for (int i = 0; i < rounds; i++)
            {
                v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + K[(sum >> 11) & 3]);
                sum -= delta;
                v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + K[sum & 3]);
            }
        }
    }
}
